#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>

#define PING_PKT_S 64
#define PING_SLEEP_RATE 1000000 // 1 second
#define MAX_PINGS 4             // 限制ping的次数

struct ping_pkt {
    struct icmphdr hdr;
    char msg[PING_PKT_S - sizeof(struct icmphdr)];
};

unsigned short checksum(void *b, int len) {
    unsigned short *buf = b;
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

void print_packet(struct ping_pkt *pkt) {
    printf("ICMP Packet: Type: %d, Code: %d, ID: %d, Sequence: %d, Checksum: %d\n",
           pkt->hdr.type, pkt->hdr.code, pkt->hdr.un.echo.id, pkt->hdr.un.echo.sequence, pkt->hdr.checksum);
}

// 使用系统命令的方式实现ping（不需要root权限）
void system_ping(const char *ip_addr) {
    char command[128];
    
    // 构建ping命令
    sprintf(command, "ping -c %d %s", MAX_PINGS, ip_addr);
    printf("使用系统ping命令: %s\n", command);
    
    // 执行系统命令
    int ret = system(command);
    if (ret != 0) {
        printf("系统ping命令执行失败，返回值: %d\n", ret);
    }
}

void raw_ping(const char *ip_addr) {
    int sockfd;
    struct sockaddr_in addr_ping;
    struct ping_pkt pkt;
    struct sockaddr_in r_addr;
    int addr_len = sizeof(r_addr);
    int msg_count = 0;
    int received_count = 0;

    // 创建原始套接字
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        fprintf(stderr, "创建原始套接字失败: %s\n", strerror(errno));
        fprintf(stderr, "提示: 在Linux系统上创建原始套接字需要root权限，请尝试使用sudo运行程序\n");
        fprintf(stderr, "或者使用程序提供的不需要root权限的系统ping方式\n");
        
        // 询问用户是否尝试使用系统ping命令
        char choice;
        printf("是否尝试使用系统ping命令? (y/n): ");
        scanf(" %c", &choice);
        if (choice == 'y' || choice == 'Y') {
            system_ping(ip_addr);
        }
        return;
    }

    memset(&addr_ping, 0, sizeof(addr_ping));
    addr_ping.sin_family = AF_INET;
    addr_ping.sin_addr.s_addr = inet_addr(ip_addr);

    // 设置接收超时
    struct timeval timeout;
    timeout.tv_sec = 5; // 5秒超时
    timeout.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));

    // 限制ping的次数
    while (msg_count < MAX_PINGS) {
        memset(&pkt, 0, sizeof(pkt));
        pkt.hdr.type = ICMP_ECHO;
        pkt.hdr.un.echo.id = getpid();
        pkt.hdr.un.echo.sequence = msg_count++;
        
        // 填充消息内容
        for (int i = 0; i < sizeof(pkt.msg) - 1; i++) {
            pkt.msg[i] = i + '0';
        }
        pkt.msg[sizeof(pkt.msg) - 1] = 0;
        
        pkt.hdr.checksum = checksum(&pkt, sizeof(pkt));

        printf("发送数据包 %d 到 %s\n", msg_count, ip_addr);
        
        // 记录发送时间
        struct timeval start_time;
        gettimeofday(&start_time, NULL);
        
        if (sendto(sockfd, &pkt, sizeof(pkt), 0, (struct sockaddr*)&addr_ping, sizeof(addr_ping)) <= 0) {
            perror("发送失败");
            usleep(PING_SLEEP_RATE);
            continue; // 继续下一次循环
        }

        // 接收ping响应
        printf("等待响应...\n");
        ssize_t bytes_received = recvfrom(sockfd, &pkt, sizeof(pkt), 0, (struct sockaddr*)&r_addr, &addr_len);
        
        // 记录接收时间
        struct timeval end_time;
        gettimeofday(&end_time, NULL);
        
        // 计算响应时间（毫秒）
        double time_ms = (end_time.tv_sec - start_time.tv_sec) * 1000.0 + 
                         (end_time.tv_usec - start_time.tv_usec) / 1000.0;
        
        if (bytes_received > 0) {
            printf("接收到 %zd 字节\n", bytes_received);
            if (pkt.hdr.type == ICMP_ECHOREPLY) {
                received_count++;
                printf("来自 %s 的回复: 字节数=%d 时间=%.2f ms\n", 
                       inet_ntoa(r_addr.sin_addr), PING_PKT_S, time_ms);
            } else {
                printf("接收到非回显回复: 类型: %d, 代码: %d\n", 
                       pkt.hdr.type, pkt.hdr.code);
            }
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("请求超时\n");
            } else {
                perror("接收失败");
            }
        }
        
        usleep(PING_SLEEP_RATE);
    }

    // 打印统计信息
    printf("\n--- %s ping 统计信息 ---\n", ip_addr);
    printf("发送 %d 个数据包, 接收 %d 个, %.0f%% 丢包\n", 
           MAX_PINGS, received_count, 
           ((float)(MAX_PINGS - received_count) / MAX_PINGS) * 100);

    close(sockfd);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("用法: %s <IP地址>\n", argv[0]);
        return 1;
    }
    
    // 检查输入的IP地址格式是否正确
    struct sockaddr_in sa;
    if (inet_pton(AF_INET, argv[1], &(sa.sin_addr)) != 1) {
        fprintf(stderr, "错误: 无效的IP地址格式\n");
        return 1;
    }
    
    printf("尝试ping %s\n", argv[1]);
    
    // 先尝试使用原始套接字方式
    raw_ping(argv[1]);
    
    return 0;
}